/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
 *
 */
import { DurationEvent, Json5Reader, MetricLogType } from '@ohos/hvigor';
import { isWindows } from '@ohos/hvigor';
import * as fse from 'fs-extra';
import path from 'path';
import { create } from 'tar';
import { PackageJson } from 'type-fest';

import { OhPackageJsonOpt, PackageJsonOpt } from '../tasks/task/ohos-har-task.js';

import { FileUtil } from './file-util.js';
import { OhosLogger } from './log/ohos-logger.js';
import { isSubPath } from '@ohos/hvigor-common';

// 参照npm源码的正则匹配
const isFileSpec = isWindows() ? /^(?:[.]|~[/]|[/\\]|[a-zA-Z]:)/ : /^(?:[.]|~[/]|[/]|[a-zA-Z]:)/;
const hasSlashes = isWindows() ? /\\|[/]/ : /[/]/;
const isFileName = /[.](?:tgz|tar.gz|tar)$/i;

/**
 * 对某个package.json文件检查其是否具有本地依赖
 * @param {string} packagePath 包路径
 * @param packageJsonObj 包解析后的对象
 * @param option 定制检查的配置
 *  - allowInside允许跳过在本目录内的本地依赖
 * @returns {boolean}
 */
export function checkHasLocalFileDependency(
  packagePath: string,
  packageJsonObj: PackageJsonOpt | OhPackageJsonOpt,
  option?: { allowInside?: boolean },
): boolean {
  const packageJson: PackageJson = packageJsonObj;
  if (packageJson.dependencies === undefined) {
    return false;
  }
  return Object.values(packageJson.dependencies).some((spec) => {
    if (option?.allowInside !== false && isLocalDependency(spec) && spec) {
      const localDepPath = /^file:(.*)/i.test(spec!) ? /^file:(.*)/i.exec(spec!)![1] : spec;
      return !FileUtil.isSubDir(path.dirname(packagePath), path.resolve(path.dirname(packagePath), localDepPath));
    } else {
      return isLocalDependency(spec);
    }
  });
}

/**
 * 对某个包收集在包内的所有本地依赖
 * @param {string} packagePath 包路径
 * @param packageJsonObj  包解析后的对象
 * @returns {string[]}
 */
export function collectLocalFileDependency(packagePath: string, packageJsonObj: PackageJsonOpt | OhPackageJsonOpt): string[] {
  const localDependencies: string[] = [];
  const packageJson: PackageJson = packageJsonObj;
  if (packageJson.dependencies === undefined) {
    return localDependencies;
  }
  Object.values(packageJson.dependencies).forEach((spec) => {
    if (isLocalDependency(spec) && spec) {
      const localDepPath = /^file:(.*)/i.test(spec!) ? /^file:(.*)/i.exec(spec!)![1] : spec;
      if (FileUtil.isSubDir(path.dirname(packagePath), path.resolve(path.dirname(packagePath), localDepPath))) {
        localDependencies.push(localDepPath);
      }
    }
  });
  return localDependencies;
}

/**
 * 判断某条路径是否为本地依赖
 * @param {string | undefined} spec
 * @returns {boolean}
 */
export function isLocalDependency(spec: string | undefined): boolean {
  return !!spec && (isFileSpec.test(spec) || /^file:/i.test(spec) || hasSlashes.test(spec) || isFileName.test(spec));
}

/**
 * 返回npm路径
 * @returns {string}
 */
export function getNpmPath(): string {
  return process.execPath;
}

/**
 * 使用tar算法对sourceDir打包, 打包产物保存在destHarPath
 *
 * @param sourceDir 待打包路径
 * @param destHarPath 打包产物路径
 * @param durationEvent 持续事件
 * @param needPrefix 是否使用package包一层
 */
export async function execOhpmPack(sourceDir: string, destHarPath: string, durationEvent: DurationEvent, needPrefix = true): Promise<void> {
  const subToolTaskName = 'execute ohpm packaging command';
  const subToolDurationEvent = durationEvent.createSubEvent(subToolTaskName, '');
  subToolDurationEvent.start();

  await create(
    {
      cwd: sourceDir,
      noDirRecurse: false,
      file: destHarPath,
      gzip: true,
      prefix: needPrefix ? 'package' : undefined,
    },
    await fse.readdir(sourceDir),
  );

  subToolDurationEvent.stop();
  subToolDurationEvent.setLog(subToolTaskName, MetricLogType.INFO);
}

const REGEX_ETS = /(.+)\.ets$/;
const REGEX_TS = /(.+)\.ts$/;
const REGEX_D = /(.+)\.d$/;

/**
 * 根据package.json的main字段，拼接出对应的types字段字符串
 *
 * @param {string | undefined} main main字段的值
 * @returns {string} types字段的值
 * @private
 */
export const getTypesFieldFromMainField = (main: string | undefined) => {
  if (main === undefined || main === '') {
    return 'index.d.ets';
  } else if (REGEX_ETS.test(main)) {
    return main.replace(REGEX_ETS, '$1.d.ets');
  } else if (REGEX_TS.test(main)) {
    return main.replace(REGEX_TS, '$1.d.ts');
  } else if (REGEX_D.test(main)) {
    return main;
  } else {
    return `${main}.d`;
  }
};

/**
 * 仅用于本地依赖参数化场景，在 parameterFile 中填写了本地产物依赖(har/hsp)时需要将依赖打进 har 包中
 * 假如产物依赖在本模块外部，需要打进 har 包的根目录
 * 假如产物依赖在本模块内部，需要保持原有的模块相对路径打进 har 包中
 *
 * @param params
 */
export function copyParameterizationLocalProductDependencies(params: {
  localProductDependencies: ReadonlyArray<string>;
  moduleDir: string;
  /** 将要压缩成 har 包的临时目录 */
  tempDir: string;
  logger: OhosLogger;
}) {

  const { localProductDependencies, moduleDir, tempDir, logger } = params;

  if (!moduleDir || !tempDir) {
    return;
  }

  logger?.debug('These local product dependencies should be copied to har.', { moduleDir, localProductDependencies });
  localProductDependencies?.forEach((depPath) => {
    if (isSubPath(depPath, moduleDir)) {
      const depRelativeToModuleDir = path.relative(moduleDir, depPath);
      const copyTo = path.resolve(tempDir, depRelativeToModuleDir);
      if (fse.existsSync(copyTo)) {
        return;
      }
      fse.ensureDirSync(path.dirname(copyTo));
      fse.copyFileSync(depPath, copyTo);
      logger?.debug('This local product dependency inside the module has been copied to har.', { depPath, moduleDir, copyTo });
    } else {
      const depFileName = path.basename(depPath);
      const copyTo =  path.resolve(tempDir, depFileName);
      if (fse.existsSync(copyTo)) {
        return;
      }
      fse.copyFileSync(depPath, copyTo);
      logger?.debug('This local product dependency outside the module has been copied to har.', { depPath, copyTo });
    }
  });
}
